﻿using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using Schedulr.Properties;
using Schedulr.Helpers;

namespace Schedulr.Tools
{
    /// <summary>
    /// Provides methods for running a Windows Forms application that is guarded against unhandled exceptions.
    /// </summary>
    public static class GuardedApplication
    {
        #region Run

        /// <summary>
        /// Starts a new Windows Forms application.
        /// </summary>
        /// <typeparam name="TMainForm">The type of form to run as the main form of the application.</typeparam>
        /// <seealso cref="Run{TMainForm}(CultureInfo, ExceptionHandlerCallback)"/>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static void Run<TMainForm>() where TMainForm : Form, new()
        {
            Run<TMainForm>(null, null);
        }

        /// <summary>
        /// Starts a new Windows Forms application.
        /// </summary>
        /// <typeparam name="TMainForm">The type of form to run as the main form of the application.</typeparam>
        /// <param name="cultureName">The name of the culture to run the application in.</param>
        /// <seealso cref="Run{TMainForm}(CultureInfo, ExceptionHandlerCallback)"/>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static void Run<TMainForm>(string cultureName) where TMainForm : Form, new()
        {
            CultureInfo culture = new CultureInfo(cultureName);
            Run<TMainForm>(culture, null);
        }

        /// <summary>
        /// Starts a new Windows Forms application.
        /// </summary>
        /// <typeparam name="TMainForm">The type of form to run as the main form of the application.</typeparam>
        /// <param name="culture">The culture to run the application in.</param>
        /// <param name="exceptionHandler">A delegate to a callback method that is invoked when an exception has occurred.</param>
        /// <remarks>
        /// <para>
        /// Before running the application using the given main form, it is ensured that all possible
        /// uncaught exceptions are handled (see http://msdn.microsoft.com/msdnmag/issues/04/06/NET/default.aspx).
        /// </para>
        /// <para>
        /// If no <paramref name="exceptionHandler"/> was given, the exceptions that are caught in this way will be shown
        /// in a messagebox.
        /// </para>
        /// <para>
        /// Furthermore, the default options for Windows Forms applications will be set to
        /// enable visual styles and to use GDI+ for text rendering.
        /// </para>
        /// </remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public static void Run<TMainForm>(CultureInfo culture, ExceptionHandlerCallback exceptionHandler) where TMainForm : Form, new()
        {
            // Determine the exception handler to use.
            if (exceptionHandler == null)
            {
                exceptionHandler = GuardedApplication.HandleException;
            }

            try
            {
                // Set some standard options.
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);

                // Set the culture.
                if (culture != null)
                {
                    Thread.CurrentThread.CurrentCulture = culture;
                    Thread.CurrentThread.CurrentUICulture = culture;
                }

                // Instantiate the main form.
                Form mainForm = new TMainForm();

                // Catch unhandled exceptions on the current thread.
                Application.ThreadException += delegate(object sender, ThreadExceptionEventArgs e)
                {
                    exceptionHandler(e.Exception);
                };

                // Catch unhandled exceptions on any thread.
                AppDomain.CurrentDomain.UnhandledException += delegate(object sender, UnhandledExceptionEventArgs e)
                {
                    Exception exc = e.ExceptionObject as Exception;

                    // The exception must be handled on the UI thread, create a delegate to invoke the handling code.
                    MethodInvoker handler = new MethodInvoker(delegate
                    {
                        if (exc != null)
                        {
                            exceptionHandler(exc);
                        }
                    });
                    if (mainForm != null)
                    {
                        // Invoke the exception handling code through the form to make sure it's
                        // executed on the UI thread.
                        mainForm.Invoke(handler);
                    }
                    else
                    {
                        // The main form is gone, just try to execute it on whatever thread we're on.
                        handler();
                    }
                };

                // Start the message loop.
                Application.Run(mainForm);
            }
            catch (Exception exc)
            {
                exceptionHandler(exc);
            }
        }

        /// <summary>
        /// Handles the exception.
        /// </summary>
        /// <param name="exc">The exception to handle.</param>
        private static void HandleException(Exception exc)
        {
            Logger.Log("An unexpected exception occurred", exc);
            if (exc is TargetInvocationException && exc.InnerException != null)
            {
                exc = exc.InnerException;
            }
            string message = Resources.ExceptionDisplayMessage;
            message = message.Replace("$(Exception.Message)", exc.Message);
            message = message.Replace("$(Exception.Type)", exc.GetType().Name);
            message = message.Replace("$(Exception.StackTrace)", exc.StackTrace);
            MessageBox.Show(message, Resources.ExceptionDisplayTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        #endregion
    }
}